/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/parser/fuze/FuzeRdp.h"
#include "simodo/parser/fuze/FuzeSblRdp.h"
#include "simodo/parser/fuze/FuzeOperationCode.h"
#include "simodo/parser/fuze/fuze_file_extension.h"
#include "simodo/parser/fuze/fuze_keywords.h"

#include "simodo/inout/token/RegularInputStreamSupplier.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/format/fmt.h"

#include <cassert>
#include <algorithm>
#include <limits>

#if __cplusplus >= __cpp_2017
#include <filesystem>
namespace fs = std::filesystem;
#else
#include <experimental/filesystem>
namespace fs = std::filesystem::experimental;
#endif

namespace simodo::parser
{

inline static std::u16string INCLUDE_STRING  {std::u16string {u"include"}};
inline static std::u16string MAIN_STRING     {std::u16string {u"main"}};
inline static std::u16string REMOVE_STRING   {std::u16string {u"remove"}};

namespace {
    inout::RegularInputStreamSupplier  regular_input_stream_supplier;
    SyntaxDataCollector_null    null_syntax_data_collector;
}

FuzeRdp::FuzeRdp(inout::Reporter_abstract & m,
                 const std::string & path,
                 ast::FormationFlow_interface & flow)
    : RdpBaseSugar(m, flow.tree().files())
    , _path(path)
    , _flow(flow)
    , _input_stream_supplier(regular_input_stream_supplier)
    , _syntax_data_collector(null_syntax_data_collector)
{
    initiateFileForParse();
}

FuzeRdp::FuzeRdp(inout::Reporter_abstract & m,
                 const std::string & path,
                 ast::FormationFlow_interface & flow,
                 inout::InputStreamSupplier_interface & input_stream_supplier,
                 SyntaxDataCollector_interface & syntax_data_collector)
    : RdpBaseSugar(m, flow.tree().files())
    , _path(path)
    , _flow(flow)
    , _input_stream_supplier(input_stream_supplier)
    , _syntax_data_collector(syntax_data_collector)
{
    initiateFileForParse();
}

bool FuzeRdp::parse()
{
    if (!_is_ready_for_parse)
        return true;

    fs::path grammar_path = _path;

    if (grammar_path.extension().empty())
        grammar_path += FUZE_FILE_EXTENSION;

    std::shared_ptr<inout::InputStream_interface> in = _input_stream_supplier.supply(grammar_path.string());

    if (!in || !in->good()) {
        reporter().reportFatal(inout::fmt("Ошибка при открытии файла '%1'").arg(grammar_path.string()));
        return false;
    }

    return parse(*in);
}

bool FuzeRdp::parse(inout::InputStream_interface & stream)
{
    if (!_is_ready_for_parse)
        return true;

    inout::LexicalParameters lex;

    lex.markups = {
        {u"/*", u"*/", u"", inout::LexemeType::Comment},
        {u"//", u"", u"", inout::LexemeType::Comment},
        {u"\"", u"\"", u"\\", inout::LexemeType::Annotation}
    };
    lex.masks = {
        {u"N", inout::LexemeType::Number, 10}
    };
    lex.punctuation_chars = u"=|;><.{}[](),";
    lex.punctuation_words = {
        INCLUDE_STRING, MAIN_STRING, REMOVE_STRING,
        ANNOTATION_STRING, NUMBER_STRING, ID_STRING,
        u"true", u"false"
    };
    lex.may_national_letters_use = true;
    lex.may_national_letters_mix = true;

    inout::Tokenizer tzer(_current_file_index, stream, lex);

    inout::Token     t = getToken(tzer);

    while(t.type() != inout::LexemeType::Empty)
    {
        while(t.type() == inout::LexemeType::Punctuation && t.lexeme() == u";")
            t = getToken(tzer);

        if (t.type() == inout::LexemeType::Empty)
            break;

        if (t.type() == inout::LexemeType::Punctuation && t.qualification() == inout::TokenQualification::Keyword)
        {
            if (!parseKeyword(tzer, t))
                return false;

            t = getToken(tzer);
        }
        else if (t.type() == inout::LexemeType::Id)
        {
            inout::Token id = t;

            t = getToken(tzer);

            if (t.type() == inout::LexemeType::Punctuation && t.lexeme() == u"{")
            {
                // ID "{" <script> "}"
                //     ^
                ast().addNode_StepInto(FUZE_HOST_NAME, static_cast<ast::OperationCode>(FuzeOperationCode::GlobalScript), id, id);

                FuzeSblRdp script_parser(reporter(), _flow, _syntax_data_collector);

                script_parser.parse(tzer, t);

                /// \todo Обрабатывать возврат false!
                ast().goParent();
            }
            else if (t.type() == inout::LexemeType::Punctuation && t.lexeme() == u"=")
            {
                // ID "=" <pattern> ";"
                //     ^
                if (!parseProduction(id, tzer, t))
                    return false;

                /// \todo Обрабатывать возврат false!
                ast().goParent();
            }
            else
                return reportUnexpected(t);
        }
        else
            return reportUnexpected(t);
    }

    _flow.finalize();

    return true;
}

bool FuzeRdp::parseKeyword(inout::Tokenizer & tzer, inout::Token & t)
{
    if ( t.lexeme() == INCLUDE_STRING)
    {
        t = getToken(tzer);

        if (t.type() == inout::LexemeType::Annotation || t.type() == inout::LexemeType::Id)
        {
            inout::Token    grammar_name_token = t;
            std::string     grammar_name       = inout::toU8(grammar_name_token.lexeme());

            t = getToken(tzer);

            if (t.type() == inout::LexemeType::Punctuation && t.lexeme() == u";")
            {
                fs::path grammar_path = _path;

                grammar_path = grammar_path.parent_path() / grammar_name;

                if (grammar_path.extension().empty())
                    grammar_path += FUZE_FILE_EXTENSION;

                if (!fs::exists(grammar_path)) {
                    reporter().reportError(grammar_name_token.makeLocation(files()), 
                                           inout::fmt("Файл грамматики '%1' не найден")
                                           .arg(grammar_path.string()));
                    return false;
                }

                /// \note Операция нужна только для передачи в семантику ссылки
                ast().addNode_StepInto(FUZE_HOST_NAME, static_cast<ast::OperationCode>(FuzeOperationCode::Reference), grammar_name_token, grammar_name_token);
                inout::Token ref_token {inout::LexemeType::Annotation, grammar_path.u16string(), t.location()};
                ast().addNode(FUZE_HOST_NAME, static_cast<ast::OperationCode>(FuzeOperationCode::None), ref_token, ref_token);
                ast().goParent();

                bool ok = FuzeRdp(reporter(), grammar_path.string(), _flow, _input_stream_supplier, _syntax_data_collector).parse();

                if (!ok)
                    reporter().reportError(grammar_name_token.makeLocation(files()), 
                                           inout::fmt("При разборе грамматики '%1' обнаружены ошибки")
                                           .arg(grammar_name_token.lexeme()));

                return ok;
            }

            return reportUnexpected(t, "';'");
        }

        return reportUnexpected(t, inout::fmt("наименование грамматики"));
    }
    else if (t.lexeme() == MAIN_STRING)
    {
        t = getToken(tzer);

        if (t.type() == inout::LexemeType::Id)
        {
            inout::Token main_production = t;

            t = getToken(tzer);

            if (t.type() == inout::LexemeType::Punctuation && t.lexeme() == u";")
            {
                ast().addNode(FUZE_HOST_NAME, static_cast<ast::OperationCode>(FuzeOperationCode::Main), main_production, main_production);
                return true;
            }

            return reportUnexpected(t, "';'");
        }

        return reportUnexpected(t, inout::fmt("идентификатор продукции"));
    }
    else if (t.lexeme() == REMOVE_STRING)
    {
        inout::Token production_token = getToken(tzer);

        if (production_token.type() != inout::LexemeType::Id)
            return reportUnexpected(t, inout::fmt("идентификатор продукции"));

        inout::Token equal_token      = getToken(tzer);

        if (!parseProduction(production_token, tzer, equal_token, FuzeOperationCode::RemoveProduction))
            return false;

        /// \todo Обрабатывать возврат false!
        ast().goParent();
        return true;
    }

    return reportUnexpected(t, 
                            inout::fmt("ключевое слово '%1', '%2' или '%3'")
                            .arg(INCLUDE_STRING)
                            .arg(MAIN_STRING)
                            .arg(REMOVE_STRING));
}

bool FuzeRdp::parseProduction(const inout::Token & id, inout::Tokenizer &tzer, inout::Token &t, FuzeOperationCode operation_code)
{
    if (t.type() != inout::LexemeType::Punctuation || t.lexeme() != u"=")
        return reportUnexpected(t, "'='");

    t = getToken(tzer);

    while(t.type() != inout::LexemeType::Empty)
    {
        ast().addNode_StepInto(FUZE_HOST_NAME, static_cast<ast::OperationCode>(operation_code), id, id);

        if (!parsePattern(tzer,t))
            return false;

        /// \todo Обрабатывать возврат false!
        ast().goParent();

        if (t.type() == inout::LexemeType::Punctuation && t.lexeme() == u";")
        {
            return true;
        }
        else if (t.type() == inout::LexemeType::Punctuation && (t.lexeme() == u"|" || t.lexeme() == u"="))
        {
            t = getToken(tzer);
            continue;
        }
        else
            return reportUnexpected(t, inout::fmt("'|', '=' или ';'"));
    }

    reporter().reportError(t.makeLocation(files()), inout::fmt("Преждевременный конец файла"));
    return false;
}

bool FuzeRdp::parsePattern(inout::Tokenizer &tzer, inout::Token &t)
{
    ast().addNode_StepInto(FUZE_HOST_NAME, static_cast<ast::OperationCode>(FuzeOperationCode::Production_Pattern), t, t);

    while(t.type() != inout::LexemeType::Empty)
    {
        if (t.type() == inout::LexemeType::Punctuation && t.qualification() != inout::TokenQualification::Keyword) {
            if (t.lexeme() == u"<" || t.lexeme() == u">") {
                /// \todo Обрабатывать возврат false!
                ast().goParent();
                ast().addNode(FUZE_HOST_NAME, static_cast<ast::OperationCode>(FuzeOperationCode::Production_Direction), t, t);

                t = getToken(tzer);

                if (t.type() == inout::LexemeType::Punctuation && t.lexeme() == u"{") {
                    ast().addNode_StepInto(FUZE_HOST_NAME, static_cast<ast::OperationCode>(FuzeOperationCode::Production_Script), t, t);

                    FuzeSblRdp script_parser(reporter(), _flow, _syntax_data_collector);
                    bool       ok = script_parser.parse(tzer, t);

                    /// \todo Обрабатывать возврат false!
                    ast().goParent();
                    return ok;
                }

                return true;
            }
            else if (t.lexeme() == u"{") {
                /// \todo Обрабатывать возврат false!
                ast().goParent();
                ast().addNode_StepInto(FUZE_HOST_NAME, static_cast<ast::OperationCode>(FuzeOperationCode::Production_Script), t, t);

                FuzeSblRdp script_parser(reporter(), _flow, _syntax_data_collector);
                bool       ok = script_parser.parse(tzer, t);

                /// \todo Обрабатывать возврат false!
                ast().goParent();
                return ok;
            }
            if (t.lexeme() == u";" || t.lexeme() == u"=" || t.lexeme() == u"|") {
                /// \todo Обрабатывать возврат false!
                ast().goParent();
                return true;
            }
            return reportUnexpected(t, inout::fmt("'символ грамматики', '<', '>', '{', '=', '|' или ';'"));
        }

        if (t.type() == inout::LexemeType::Id || t.type() == inout::LexemeType::Annotation
         || t.qualification() == inout::TokenQualification::Keyword) {
            ast().addNode(FUZE_HOST_NAME, static_cast<ast::OperationCode>(FuzeOperationCode::None), t, t);
            t = getToken(tzer);
        }
        else
            return reportUnexpected(t, inout::fmt("'символ грамматики', '<', '>', '{', '=', '|' или ';'"));
    }

    /// \todo Обрабатывать возврат false!
    ast().goParent();
    return true;
}

inout::Token FuzeRdp::getToken(inout::Tokenizer & tzer) const
{
    inout::Token token = tzer.getAnyToken();

    while (token.type() == inout::LexemeType::Comment) {
        _syntax_data_collector.collectToken(token);

        token = tzer.getAnyToken();
    }

    _syntax_data_collector.collectToken(token);

    return token;
}

void FuzeRdp::initiateFileForParse()
{
    fs::path grammar_path = _path;

    if (grammar_path.extension().empty())
        grammar_path += FUZE_FILE_EXTENSION;

   _is_ready_for_parse = std::find(files().begin(),files().end(),grammar_path.string()) == files().end();

    if (_is_ready_for_parse) {
        assert(files().size() <= std::numeric_limits<inout::uri_index_t>::max());
        _current_file_index = files().size();
        _flow.addFile(grammar_path.string());
    }
    else
        _current_file_index = 0;
}

}